import tkinter as tk
from tkinter import font
from tkinter import messagebox
import calculate_tool
from typing import Generator, List


class FontManager:

    fonts: dict = {}
    
    def setRoot(self, root):
        self.root = root
        self.createFont()

    def createFont(self):
        font_entry = font.Font(family='Microsoft YaHei UI', size=12)
        self.fonts['entry'] = font_entry
        font_btn = font.Font(family="Microsoft YaHei UI", size=9)
        self.fonts['btn'] = font_btn

    def getFont(self, name):
        return self.fonts.get(name)
    
fontManager = FontManager()


class FramedButton(tk.Frame):

    button: tk.Button

    def __init__(self, master, btn_text, command):
        super().__init__(master)

        self.button = tk.Button(self, 
                               text=btn_text, 
                            #    font=fontManager.getFont('btn'), 
                               width=10, 
                               pady=0,
                               command=command)
        self.button.pack(fill=tk.BOTH, expand=True, padx=20, pady=10)
        self.button.pack_propagate(False)

    def __repr__(self):
        return f"<FramedButton {self.button}>"

class HeadFootGrid(tk.Frame):
    def __init__(self, master):
        super().__init__(master)
        self.grid_columnconfigure(0, weight=1)
        self.grid_rowconfigure(1, weight=1)

    def setHead(self, component: tk.Widget):
        component.grid(row=0, column=0, sticky='ew')

    def setBody(self, component: tk.Widget):
        component.grid(row=1, column=0, sticky='nsew')

    def setTail(self, component: tk.Widget):
        component.grid(row=2, column=0, sticky='ew')

class ParameterComponent(tk.Frame):

    param_comp: tk.Entry
    var_comp: tk.Entry

    def __init__(self, master: tk.Frame, on_delete=None, on_entry_input=None):
        super().__init__(master)

        self.pack(fill=tk.X, expand=False)

        column_count = 0

        label_parameter = tk.Label(self, text="系数", width=10)
        label_parameter.grid(row=0, column=column_count)
        column_count += 1

        self.param_comp = entry_parameter = tk.Entry(self, 
                                #    font=fontManager.getFont('entry'), 
                                   width=8)
        entry_parameter.grid(row=0, column=column_count)
        entry_parameter.bind("<KeyRelease>", on_entry_input)
        column_count += 1


        label_x = tk.Label(self, text="*", width=10)
        label_x.grid(row=0, column=column_count)
        column_count += 1

        label_variable = tk.Label(self, text="整数", width=10)
        label_variable.grid(row=0, column=column_count)
        column_count += 1

        self.var_comp = entry_variable = tk.Entry(self, 
                                  state="readonly",
                                   width=8)
        
        entry_variable.grid(row=0, column=column_count)
        column_count += 1
        

        button_parameter = FramedButton(self, "删除", lambda: on_delete(self))
        button_parameter.grid(row=0, column=column_count, sticky=tk.E)
        

        self.grid_columnconfigure(column_count, weight=1)

        column_count += 1
        
    def getValue(self)-> float:
        val = self.param_comp.get()
        if val == "":
            return 0
        try:
            return float(val)
        except ValueError as error:
            messagebox.showerror("错误", f"{val}不是数字")
            raise error
        

    def setIntValue(self, val):
        self.var_comp.config(state="normal")
        self.var_comp.delete(0, tk.END)
        self.var_comp.insert(0, val)
        self.var_comp.config(state="readonly")

    def __repr__(self) -> str:
        return f"<ParameterComponent>"
    
    

class App(tk.Tk):

    parameter_box: tk.Frame

    # 参数组件列表
    param_comps = []

    calculate_ctx: Generator

    def __init__(self):
        super().__init__()
        self.calculate_ctx = None

        self.withdraw()

        self.initialize()

        center_window(self)

        self.deiconify()


    def initialize(self):
        self.title("整数参数反推计算器")
        self.minsize(640, 320)
        
        
        root_frame = HeadFootGrid(self)
        root_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)

        total_box = self.place_totalBox(root_frame)
        root_frame.setHead(total_box)

        list_box = self.place_list_parameter(root_frame)
        root_frame.setBody(list_box)
        
        calc_button = self.place_calc_button(root_frame)
        root_frame.setTail(calc_button)

        self.update()

    def place_totalBox(self, root):
        """
        创建一个用于放置总和输入框的框架
        """

        # 创建一个名为“total_box”的框架，用于存放总和输入框
        frame_total_box = tk.Frame(root)

        # # 设置框架中的第一列具有扩展性
        # frame_total_box.grid_columnconfigure(1, weight=1)

        # 创建一个标签，用于提示输入总和
        label_total = tk.Label(frame_total_box, text="请输入总和：", width=15)
        # 将标签放置在框架的第0行第0列，且文本靠左
        label_total.grid(row=0, column=0, sticky='w')
        
        # 创建一个输入框，用于输入总和
        self.target_comp = entry_total = tk.Entry(frame_total_box, width=15)
        # 将输入框放置在框架的第0行第1列，且文本靠左
        entry_total.grid(row=0, column=1, sticky="w")
        entry_total.bind("<KeyRelease>", self.on_key_release)

        label_int = tk.Label(frame_total_box, text="请输入整数和:", width=15)
        label_int.grid(row=0, column=2, sticky="w")

        self.total_comp = entry_int = tk.Entry(frame_total_box, width=15)
        entry_int.grid(row=0, column=3, sticky="w")
        entry_int.bind("<KeyRelease>", self.on_key_release)


        # 创建一个按钮框架，用于增加参数组件
        frame_button_add_wrap = FramedButton(frame_total_box, "增加参数", self.add_parameter_component)
        # 将按钮框架放置在框架的第0行第2列
        frame_button_add_wrap.grid(row=0, column=4, sticky=tk.E, padx=18)
        frame_total_box.grid_columnconfigure(4, weight=1)

        return frame_total_box

    def place_list_parameter(self, root):
        """
        在给定的根窗口中创建并放置列表参数框。
        """

        # 创建参数框框架，并配置其大小、名称和边框样式
        frame_parameter_box = tk.Frame(root, 
                                    #    width=640, 
                                       height=90,
                                        highlightbackground="darkgray", highlightthickness=1)

        scroll_canvas = tk.Canvas(frame_parameter_box,  
                                #   width=640,
                                  bd=0, 
                                  highlightthickness=0,
                                  takefocus=False)
        scroll_canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)


        self.parameter_box = inner_frame = tk.Frame(scroll_canvas)
        inner_frame.pack(fill=tk.BOTH, expand=True)
        
        window_id = scroll_canvas.create_window((0, 0), window=inner_frame, anchor=tk.NW)

        def on_canvas_configure(event):
            width = event.width
            scroll_canvas.itemconfigure(window_id, width=width)

        scroll_canvas.bind("<Configure>", on_canvas_configure)

        scroller = tk.Scrollbar(frame_parameter_box, orient=tk.VERTICAL, command=scroll_canvas.yview)
        scroller.pack(side=tk.RIGHT, fill=tk.Y)
        scroll_canvas.config(yscrollcommand=scroller.set)

        def on_frame_configure(event):
            bbox =scroll_canvas.bbox("all")
            scroll_canvas.configure(scrollregion=bbox)

        inner_frame.bind("<Configure>", on_frame_configure)

        # 添加子组件
        self.parameter_component(self.parameter_box)
        self.parameter_component(self.parameter_box)

        return frame_parameter_box

    def place_calc_button(self, root: tk.Frame):
        frame_calc_btn_box = tk.Frame(root)

        self.button_calc = button_calc = tk.Button(frame_calc_btn_box, 
                                text="计算", 
                                width=10, 
                                pady=0,
                                command=self.calculate)
        button_calc.pack(fill='none', expand=True, anchor=tk.CENTER, pady=10)

        return frame_calc_btn_box
    
    def reset_context(self):
        self.button_calc.config(text="计算")
        self.calculate_ctx = None

    def on_key_release(self, event):
        self.reset_context()

    def calculate(self):

        if self.calculate_ctx is None:
        
            try:
                target = float(self.target_comp.get())
            except ValueError:
                messagebox.showerror("错误", "总和不是数字")
                return
            
            try:
                total = int(self.total_comp.get())
            except ValueError:
                messagebox.showerror("错误", "整数和输入错误")
                return
            
            try:
                params: List = [comp.getValue() for comp in self.param_comps]
            except ValueError:
                return
            
            self.calculate_ctx = calculate_tool.exec(target, total, params)

        try:
            ctx = next(self.calculate_ctx)

            if ctx.finish:
                messagebox.showwarning("计算结果", "计算结束")
                self.reset_context()

            else:
                messagebox.showinfo("计算结果", "计算完成")
                self.button_calc.config(text="继续计算")

            list = ctx.count_list
            for i,comp in enumerate(self.param_comps):
                comp.setIntValue(list[i])

        except StopIteration:
            messagebox.showerror("计算结果", "计算中止")
            self.reset_context()
            return
        


    def delete_parameter_component(self, component: tk.Widget):
        self.param_comps.remove(component)
        component.destroy()
        self.reset_context()

    def parameter_component(self, root):
        frame_parameter = ParameterComponent(root, on_delete=self.delete_parameter_component, on_entry_input=self.on_key_release)
        self.param_comps.append(frame_parameter)
        

    def add_parameter_component(self):
        self.parameter_component(self.parameter_box)
        self.reset_context()
        
    


def center_window(window: tk.Widget):

    # 获取屏幕宽度和高度  
    screen_width = window.winfo_screenwidth()  
    screen_height = window.winfo_screenheight()  
    
    # 获取窗口自身的宽度和高度  
    width = window.winfo_width()
    height = window.winfo_height() 
    
    # 计算x和y坐标，使得窗口居中  
    x = (screen_width // 2) - (width // 2)  
    y = (screen_height // 2) - (height // 2)  
    
    # 设置窗口位置  
    window.geometry(f'+{x}+{y}') 
    

if __name__ == '__main__':

    root = App()
    # fontManager.setRoot(root)
    center_window(root)

    root.mainloop()